﻿using System;
using System.Linq;
using System.Collections.Generic;
using TeamCityMonitor.Models;
using TeamCitySharp;
using TeamCitySharp.DomainEntities;

namespace TeamCityMonitor.Repository
{
    public interface IBuildRepository
    {
        List<ProjectModel> GetAllTroubledProjectModels();
        IEnumerable<User> GetUsers();
        List<string> GetRunningBuildConfigIds();
        Server GetServerInfo();

        int GetNoOfProjects();
        int GetNoOfConfigurations();

        int GetNoOfSuccessfullBuildsSince(int daysBack);
        int GetNoOfFailingBuildsSince(int daysBack);
        int GetNoOfErrorBuildsSince(int daysBack);
        int GetNoOfBuildsByUser(User user);

        IEnumerable<BuildConfig> GetBuildConfigurations();
        int GetNoOfSuccessfullBuildsByBuildConfig(BuildConfig config);
        int GetNoOfFailingBuildsByBuildConfig(BuildConfig config);
        int GetNoOfErrorBuildsByBuildConfig(BuildConfig config);
    }

    public class BuildRepository: IDisposable, IBuildRepository
    {
        private TeamCityClient client;
        private List<Project> activeProjects;
        private List<BuildConfig> buildConfigs;
        private List<Build> troubledBuilds;
        private List<User> users;
        private List<Change> changes;
        private List<TroubleEntry> troubledEntries;

        public BuildRepository()
        {
            client = new TeamCityClient(Properties.Settings.Default.TeamCityURL, false);
            client.Connect(
                              Properties.Settings.Default.TeamCityUser,
                              Properties.Settings.Default.TeamCityPwd,
                              false
                          );

            activeProjects = GetActiveProjects();
            buildConfigs = GetActiveBuildConfigurations();
            users = client.AllUsers();
            troubledBuilds = GetTroubledBuilds();
            troubledEntries = GetTroublesEntities();
        }

        private List<Project> GetActiveProjects()
        {
            return client.AllProjects()
                            .Select(project => client.ProjectDetails(project)).ToList()
                                                     .Where(ProjectIsNotArchived)
                                                        .Where(ProjectHasConfigurations)
                                                            .ToList();
        }

        private bool ProjectIsNotArchived(Project project)
        {
            return project.Archived == false;
        }

        private bool ProjectHasConfigurations(Project project)
        {
            return project.BuildTypes != null;
        }

        private List<BuildConfig> GetActiveBuildConfigurations()
        {
            buildConfigs = new List<BuildConfig>();
            activeProjects.ForEach(project => buildConfigs.AddRange(project.BuildTypes.BuildType));
            return buildConfigs;
        }

        private List<Build> GetTroubledBuilds()
        {
            var latestBuilds = GetLatestBuilds();

            latestBuilds = HandleBuildsInUnknownStates(latestBuilds);
            latestBuilds = HandleBuildsWhichHaveNeverBeenBuild(latestBuilds);

            return latestBuilds.Where(build => build.Status != "SUCCESS").ToList();
        }

        private List<Build> HandleBuildsInUnknownStates(List<Build> builds)
        {
            if (Properties.Settings.Default.ShowBuildsWhichAreInAnUnknownState == true)
            {
                return builds;
            }
            return builds.Where(build => build.Status != "UNKNOWN").ToList();
        }

        private List<Build> HandleBuildsWhichHaveNeverBeenBuild(List<Build> builds)
        {
            if (Properties.Settings.Default.ShowBuildConfigsWhichHaveNeverBeenBuild == true)
            {
                return builds;
            }
            return builds.Where(build => build.Status != "NEVERBUILD").ToList();
        }


        private List<Build> GetLatestBuilds()
        {
            var latestBuilds = new List<Build>();

            buildConfigs.ForEach(config =>
            {
                var build = GetLastBuild(config);
                build = AttemptToGetPreviousBuildInKnownStateIfCurrenctBuildStatusIsUnknown(build);
                latestBuilds.Add(build);
            });
            return latestBuilds;
        }

        private Build GetLastBuild(BuildConfig config)
        {
            var build = new Build { Status = "NEVERBUILD", BuildConfig = config };
            try
            {
                var latestBuild = client.LastBuildByBuildConfigId(config.Id);
                latestBuild.BuildConfig = config;

                if (string.IsNullOrEmpty(latestBuild.Id) == false)
                {
                    build = latestBuild;
                }
            }
            catch { }
            return build;
        }

        private Build AttemptToGetPreviousBuildInKnownStateIfCurrenctBuildStatusIsUnknown(Build build)
        {
            var builds = new List<Build> { build };

            if (string.IsNullOrEmpty(build.Status) || build.Status == "UNKNOWN")
            {
                builds.Add(GetLastErrorBuild(build.BuildConfig));
                builds.Add(GetLastFailedBuild(build.BuildConfig));
                builds.Add(GetLastSuccessfullBuild(build.BuildConfig));

                builds = builds.Where(bld => bld.Status != "BADSTATE").ToList()
                                .OrderByDescending(bld => bld.StartDate).ToList();
            }

            return builds.First();
        }

        private Build GetLastErrorBuild(BuildConfig config)
        {
            var build = new Build { Status = "BADSTATE" };
            try
            {
                build = client.LastErrorBuildByBuildConfigId(config.Id);
                build.BuildConfig = config;
            }
            catch{}
            return build;
        }

        private Build GetLastFailedBuild(BuildConfig config)
        {
            var build = new Build { Id = "BADSTATE" };
            try
            {
                build = client.LastFailedBuildByBuildConfigId(config.Id);
                build.BuildConfig = config;
            }
            catch { }
            return build;
        }

        private Build GetLastSuccessfullBuild(BuildConfig config)
        {
            var build = new Build { Id = "BADSTATE" };
            try
            {
                build = client.LastSuccessfulBuildByBuildConfigId(config.Id);
                build.BuildConfig = config;
            }
            catch { }
            return build;
        }

        public List<string> GetRunningBuildConfigIds()
        {
            var builds = client.BuildsByBuildLocator(BuildLocator.RunningBuilds());

            return (builds != null ? builds.ToList().Select(build => build.BuildTypeId).ToList() : new List<string>());
        }

        public Server GetServerInfo()
        {
            return client.ServerInfo();
        }

        private List<TroubleEntry> GetTroublesEntities()
        {
            var troubles = new List<TroubleEntry>();

            troubledBuilds.ForEach(build => troubles.Add(new TroubleEntry
            {
                Project = activeProjects.First(project => project.Id == build.BuildConfig.ProjectId),
                Config = build.BuildConfig,
                Build = build
            }));

            return troubles;
        }

        public List<ProjectModel> GetAllTroubledProjectModels()
        {
            return troubledEntries.Select(troubledEntry => GenerateProjectModelFromConfigAndProject(troubledEntry)).ToList();
        }

        private ProjectModel GenerateProjectModelFromConfigAndProject(TroubleEntry entry)
        {
            var change = GetLastChangeByBuildConfigId(entry.Config.Id);

            var model = new ProjectModel()
            {
                LastBuildStatus = (entry.Build.Status ?? "UNKNOWN"),
                BuildStatusCause = GetBuildStatusCause(entry.Build),
                DisplayTitle = string.Format("{0}, {1}", entry.Project.Name, entry.Config.Name),
                LastChangeUser = GetUsername(change),
                LastChangeComment = GetLastChangeComment(change),
                LastChangeUserPictureUrl = GetUserPictureUrl(change.Username),
                BuildId = entry.Build.Id,
                BuildConfigId = entry.Config.Id,
                ProjectId = entry.Project.Id,
                IsRunning = false
            };

            return model;            
        }

        private string GetLastChangeComment(Change change)
        {
            if (change != null)
            {
                return (string.IsNullOrEmpty(change.Comment) == false
                            ? string.Format("Last commit log: {0}", change.Comment)
                            : "Last commit log: (No commit log found)");
            }
            return "Last commit log: (No commit log found)";
        }

        private string GetUsername(Change change)
        {
            if (change != null)
            {
                return string.Format("{0}{1}", Properties.Settings.Default.BuildTroublesMessage, change.Username);
            }
            return string.Format("{0}{1}",Properties.Settings.Default.BuildTroublesMessage, "(No history avaliable)");
        }

        private string GetUserPictureUrl(string username)
        {
            var url = "../../Content/empty-profile.jpg";

            if (Properties.Settings.Default.UsernamePictureUrl != "http://myintranet/users/{0}.jpg" &&
                Properties.Settings.Default.UsernamePictureUrl.Contains("{0}") &&
                string.IsNullOrEmpty(username) == false)
            {
                url = string.Format(Properties.Settings.Default.UsernamePictureUrl, username);
            }
            return url;
        }

        public int GetNoOfProjects()
        {
            return activeProjects.Count;
        }

        public int GetNoOfConfigurations()
        {
            return buildConfigs.Count;
        }

        private Change GetLatestChangeDetailsByChangeId(string buildId)
        {
            var change = new Change() { Username = "(No change/user found)", Comment = "" };
            try
            {
                change = client.ChangeDetailsByChangeId(buildId);
            }
            catch {}
            return change;
        }

        private Change GetLastChangeByBuildConfigId(string buildConfigId)
        {
            var change = new Change() { Username = "(No change/user found)", Comment = "" };
            try
            {
                change = client.LastChangeByBuildConfigId(buildConfigId);
            }
            catch { }

            return (string.IsNullOrEmpty(change.Id) == false ? GetLatestChangeDetailsByChangeId(change.Id) : change);
        }

        private string GetBuildStatusCause(Build build)
        {
            var cause = "";

            switch (build.Status)
            {
                case "SUCCESS":
                    break;
                case "FAILURE":
                    cause = "This configuration is failing.";
                    break;
                case "ERROR":
                    cause = "This configuration has an error.";
                    break;
                case "NEVERBUILD":
                    cause = "This configuration has never been build.";
                    break;
                case "UNKNOWN":
                    cause = "This configuration is in an unknown state.";
                    break;
                default:
                    cause = "This configuration is in an unknown state.";
                    break;
            }

            return cause;
        }

        public IEnumerable<User> GetUsers()
        {
            return users;
        }

        public int GetNoOfSuccessfullBuildsSince(int daysBack)
        {
            var builds = client.AllBuildsOfStatusSinceDate(DateTime.Now.AddDays(-daysBack), BuildStatus.SUCCESS);

            return (builds == null ? 0 : builds.Count());
        }

        public int GetNoOfFailingBuildsSince(int daysBack)
        {
            var builds = client.AllBuildsOfStatusSinceDate(DateTime.Now.AddDays(-daysBack), BuildStatus.FAILURE);

            return (builds == null ? 0 : builds.Count());
        }

        public int GetNoOfErrorBuildsSince(int daysBack)
        {
            var builds = client.AllBuildsOfStatusSinceDate(DateTime.Now.AddDays(-daysBack), BuildStatus.ERROR);

            return (builds == null ? 0 : builds.Count());
        }

        public int GetNoOfBuildsByUser(User user)
        {
            var builds = client.BuildsByUserName(user.Username);
            return (builds == null ? 0 : builds.Count());
        }

        public IEnumerable<BuildConfig> GetBuildConfigurations()
        {
            return buildConfigs;
        }

        public int GetNoOfSuccessfullBuildsByBuildConfig(BuildConfig config)
        {
            var builds = client.SuccessfulBuildsByBuildConfigId(config.Id);
            return (builds == null ? 0 : builds.Count());
        }

        public int GetNoOfFailingBuildsByBuildConfig(BuildConfig config)
        {
            var builds = client.FailedBuildsByBuildConfigId(config.Id);
            return (builds == null ? 0 : builds.Count());
        }

        public int GetNoOfErrorBuildsByBuildConfig(BuildConfig config)
        {
            var builds = client.ErrorBuildsByBuildConfigId(config.Id);
            return (builds == null ? 0 : builds.Count());
        }

        public void Dispose()
        {
            this.activeProjects = null;
            this.buildConfigs = null;
            this.changes = null;
            this.client = null;
            this.troubledBuilds = null;
            this.troubledEntries = null;
            this.users = null;
        }

        private class TroubleEntry
        {
            public Project Project { get; set; }
            public BuildConfig Config { get; set; }
            public Build Build { get; set; }
        }
    }
}